import type { FlowNode, FlowEdge, JDFAttribute } from '@/models/jdf';
import { ElMessage } from 'element-plus';
import yaml from 'js-yaml';
import toml from '@iarna/toml';

// --- XML Generation --- 

/**
 * Generates an XML snippet for a given node and optionally its descendants.
 */
export function generateXmlSnippet(nodes: FlowNode[], edges: FlowEdge[], startNodeId: string, includeChildren: boolean): string {
    const nodeMap = new Map<string, FlowNode>(nodes.map(n => [n.id, n]));
    const childrenMap = new Map<string, string[]>();

    if (includeChildren) {
        edges.forEach(edge => {
            if (!childrenMap.has(edge.source)) {
                childrenMap.set(edge.source, []);
            }
            childrenMap.get(edge.source)!.push(edge.target);
        });
    }

    const buildXml = (nodeId: string, indentLevel: number = 0): string => {
        const node = nodeMap.get(nodeId);
        if (!node) return '';

        const indent = '  '.repeat(indentLevel);
        let xml = `${indent}<${node.data.nodeName}`;

        node.data.attributes.forEach(attr => {
            const escapedValue = escapeXmlValue(attr.value);
            xml += ` ${attr.name}=\"${escapedValue}\"`;
        });

        const childrenIds = includeChildren ? (childrenMap.get(nodeId) || []) : [];
        const textContent = node.data.textContent?.trim();

        if (childrenIds.length === 0 && !textContent) {
            xml += '/>\n';
        } else {
            xml += '>';
            if (childrenIds.length > 0) {
                xml += '\n';
                childrenIds.forEach(childId => {
                    xml += buildXml(childId, indentLevel + 1);
                });
                xml += `${indent}</${node.data.nodeName}>\n`;
            } else if (textContent) {
                xml += escapeXmlText(textContent) + `</${node.data.nodeName}>\n`;
            }
        }
        return xml;
    };

    return buildXml(startNodeId).trim(); // Trim final newline
}

function escapeXmlValue(value: string): string {
    return value
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/\'/g, '&apos;');
}

function escapeXmlText(text: string): string {
    return text
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
}


// --- JSON / Plain Object Conversion --- 

interface SimpleNode {
    nodeName: string;
    attributes?: Record<string, string>;
    textContent?: string;
    children?: SimpleNode[];
}

/**
 * Converts a FlowNode's data to a simple JSON-like object (attributes as map).
 */
function nodeDataToJson(data: FlowNode['data']): SimpleNode {
    const obj: SimpleNode = { nodeName: data.nodeName };
    if (data.attributes.length > 0) {
        obj.attributes = data.attributes.reduce((acc, attr) => {
            acc[attr.name] = attr.value;
            return acc;
        }, {} as Record<string, string>);
    }
    if (data.textContent) {
        obj.textContent = data.textContent;
    }
    return obj;
}

/**
 * Recursively converts a node and its descendants to a nested JSON-like structure.
 */
export function nodeWithChildrenToJson(nodes: FlowNode[], edges: FlowEdge[], startNodeId: string): SimpleNode | null {
    const nodeMap = new Map<string, FlowNode>(nodes.map(n => [n.id, n]));
    const childrenMap = new Map<string, string[]>();
    edges.forEach(edge => {
        if (!childrenMap.has(edge.source)) {
            childrenMap.set(edge.source, []);
        }
        childrenMap.get(edge.source)!.push(edge.target);
    });

    const buildJson = (nodeId: string): SimpleNode | null => {
        const node = nodeMap.get(nodeId);
        if (!node) return null;

        const jsonObj = nodeDataToJson(node.data);
        const childrenIds = childrenMap.get(nodeId) || [];

        if (childrenIds.length > 0) {
            jsonObj.children = childrenIds
                .map(childId => buildJson(childId))
                .filter(child => child !== null) as SimpleNode[];
            if (jsonObj.children.length === 0) delete jsonObj.children; // Clean up empty array
        }
        return jsonObj;
    };

    return buildJson(startNodeId);
}

/**
 * Converts only the specified node's data to a simple JSON-like object.
 */
export function nodeToJson(nodes: FlowNode[], nodeId: string): SimpleNode | null {
     const node = nodes.find(n => n.id === nodeId);
     return node ? nodeDataToJson(node.data) : null;
}


// --- Clipboard Utility --- 

/**
 * Copies text to the clipboard using the Clipboard API.
 */
export async function copyToClipboard(text: string, formatName: string): Promise<void> {
    try {
        await navigator.clipboard.writeText(text);
        ElMessage.success(`节点已复制为 ${formatName}`);
    } catch (err) {
        console.error('Failed to copy to clipboard:', err);
        ElMessage.error('复制到剪贴板失败');
    }
}

// --- Format Conversion --- 

export function convertToJsonString(data: SimpleNode | null): string {
    return data ? JSON.stringify(data, null, 2) : '';
}

export function convertToYamlString(data: SimpleNode | null): string {
    return data ? yaml.dump(data) : '';
}

export function convertToTomlString(data: SimpleNode | null): string {
     if (!data) return '';
     try {
        // TOML requires a top-level table, wrap the node data
        // Convert to JSON string and back to bypass strict typing
        const plainJsonObject = JSON.parse(JSON.stringify({ node: data }));
        return toml.stringify(plainJsonObject);
     } catch (err) {
         console.error("TOML Conversion Error:", err);
         ElMessage.error('转换为 TOML 失败');
         return '';
     }
} 